#ifndef __SLOTHTTPD_FCGI_MULTIPLEXER_HPP__
#define __SLOTHTTPD_FCGI_MULTIPLEXER_HPP__

#include <slothttpd/types.hpp>
#include <slothttpd/data_buffer.hpp>
#include <slothttpd/fcgi/fcgi.hpp>

#include <cstdint>
#include <vector>

#include <boost/array.hpp>
#include <boost/intrusive_ptr.hpp>
#include <boost/system/error_code.hpp>
#include <boost/intrusive/list.hpp>
#include <boost/function.hpp>
#include <boost/asio.hpp>
#include <boost/noncopyable.hpp>

namespace slothttpd {
namespace fcgi {

class application;

class multiplexer;
typedef boost::intrusive_ptr<multiplexer> multiplexer_ptr;

class multiplexer :
        private boost::noncopyable,
        public boost::intrusive::list_base_hook<boost::intrusive::link_mode<boost::intrusive::auto_unlink> >
{
    friend class application;
    friend void intrusive_ptr_add_ref(multiplexer*) noexcept;
    friend void intrusive_ptr_release(multiplexer*) noexcept;
    
private:
    class request_id :
            public boost::noncopyable,
            public boost::intrusive::list_base_hook<boost::intrusive::link_mode<boost::intrusive::auto_unlink> >
    {
    public:
        typedef boost::function<void(char*, const boost::system::error_code&)> handler_type;
        typedef boost::function<char_range_t()> get_write_buffer_type;
        
        request_id() noexcept;
        
    public:
        multiplexer *m;
        std::uint16_t id;
        
        receive_buffer_list *stdout_buffers;
        receive_buffer_list *stderr_buffers;
        
        /**
         * @brief write_buffer should return 2 sequences of buffers with size() > 0.
         * End of sequeece is buffer with size() == 0. Sequence buffers should have
         * size() <= fcgi::max_content_length.
         */
        get_write_buffer_type write_buffer;
        
        // read_handler (called on data received) or on error
        handler_type   handler;
    };
    
    enum write_state_type {
        write_not_active,
        write_sending_begin_request,
        write_sending_params,
        write_sending_params_end,
        write_sending_stdin,
        write_sending_stdin_end,
        write_abort
    };
    
    typedef boost::intrusive::list<request_id, boost::intrusive::constant_time_size<false> > request_id_list;
    typedef boost::function<void()> skip_handler_type;
    
public:
    class request_controller
    {
    public:
        inline request_controller() : id_(nullptr) { }
        inline request_controller(request_id *id) : id_(id) { }
        inline bool is_active() const { return id_ != nullptr; }
        inline std::uint16_t id() const { return id_->id; }
        
        inline void disconnect() { id_ = nullptr; }
        // TODO: add abort request
        
    private:
        request_id *id_;
    };
    
private:
    request_id *acquire_request();
    void reset_request(request_id &r);
    void notify_error(request_id_list &list, const boost::system::error_code &err);
    void enqueue_write(request_id &r);
    request_id *find_request_id(std::uint16_t id);
    
public:
    multiplexer(application *app);
    bool full() const;
    void close();
    
    request_controller begin_request(
            receive_buffer_list *stdout,
            receive_buffer_list *stderr, 
            request_id::get_write_buffer_type get_buffers,
            request_id::handler_type handler);
    
private:
    static void handle_connect(multiplexer_ptr m, const boost::system::error_code &err);
    
    void post_write_record(std::uint16_t id, std::uint8_t t, char *b, std::size_t len);
    void post_write();
    std::size_t post_next_buffer(); // return true if sent 0 length buffer
    static void handle_write(multiplexer_ptr m, const boost::system::error_code &err);
    
    void post_read_header();
    static void handle_read_header(multiplexer_ptr m, const boost::system::error_code &err);
    
    void post_read_control_record();
    static void handle_read_control_record(multiplexer_ptr m, const boost::system::error_code &err);
    
    void post_read_content(request_id *req_id, std::uint16_t bytes, std::uint8_t type);
    void post_read_content();
    static void handle_read_content(multiplexer_ptr m, std::size_t bytes, const boost::system::error_code &err);
    
    void post_skip_padding();
    
    void post_skip_data(); // skip 'skip_left_' bytes
    
    template<typename Handler>
    void post_skip_data(std::size_t bytes, Handler handler)
    {
        skip_left_      = bytes;
        skip_complete_  = skip_handler_type(handler);
        
        post_skip_data();
    }
    
    static void handle_skip_data(multiplexer_ptr m, const boost::system::error_code &err);
    
    void on_read_error(const boost::system::error_code &err, long line);
    void on_write_error(const boost::system::error_code &err, long line);
    
private:
    boost::asio::local::stream_protocol::socket socket_;
    
    write_state_type    write_state_;
    request_id_list     write_pending_ids_;
    boost::array<char, fcgi::control_record_len> write_header_buffer_;
    boost::array<boost::asio::const_buffer, 2> two_buffers_;
    
    
    boost::array<char, fcgi::control_record_len> read_header_buffer_;
    std::size_t             read_left_;
    receive_buffer_list    *read_dst_;
    request_id             *read_current_;
    request_id_list         read_pending_ids_;
    
    std::size_t             skip_left_;
    skip_handler_type       skip_complete_;
    
    request_id_list         free_ids_;
    
    application            *app_;
    
    std::vector<request_id> id_container_;
    unsigned long ref_count_;
};

}
}

#endif
